#include "sdl-gui.hpp"

namespace sdl
{
namespace gui
{

sdl::Surface* Element::target = NULL;

Element::Element()
{
    id = "";
    pos = Rect();
}

void Element::drawFrame(bool inverted, Rect box)
{
    if (inverted)
    {
        target->fill(Rect(box.x, box.y, box.w, 1), Color(15, 15, 15, 128));
        target->fill(Rect(box.x, box.y, 1, box.h), Color(15, 15, 15, 128));
        target->fill(Rect(box.x, box.y + box.h - 1, box.w, 1), Color(240, 240, 240, 128));
        target->fill(Rect(box.x + box.w - 1, box.y, 1, box.h), Color(240, 240, 240, 128));
    }
    else
    {
        target->fill(Rect(box.x, box.y, box.w, 1), Color(240, 240, 240, 128));
        target->fill(Rect(box.x, box.y, 1, box.h), Color(240, 240, 240, 128));
        target->fill(Rect(box.x, box.y + box.h - 1, box.w, 1), Color(15, 15, 15, 128));
        target->fill(Rect(box.x + box.w - 1, box.y, 1, box.h), Color(15, 15, 15, 128));
    }
}

Label::Label() : Element(), Text()
{
    align = AlignCentre;
    background = Color();
}

void Label::render()
{
    render(pos);
}


void Label::render(Rect tpos, bool offset)
{
    redraw();
    Rect clip;
    Rect dest;
    dest.x = tpos.x;
    clip.y = 0;
    clip.h = tpos.h;
    dest.y = tpos.y;
    if (tpos.h > size().h)
        dest.y += (tpos.h - size().h) / 2;
    switch (align)
    {
    case AlignLeft:
        clip.x = 0;
        clip.w = tpos.w;
        break;
    case AlignCentre:
        if (size().w > tpos.w)
        {
            clip.x = (size().w - tpos.w) / 2;
            clip.w = tpos.w;
        }
        else
        {
            dest.x += (tpos.w - size().w) / 2;
            clip.w = size().w;
        }
        break;
    case AlignRight:
        if (size().w > tpos.w)
        {
            clip.x = size().w - tpos.w;
            clip.w = tpos.w;
        }
        else
        {
            dest.x += tpos.w - size().w;
            clip.w = size().w;
        }
        break;
    }
    if (offset)
    {
        dest.x += 1;
        dest.y += 1;
        clip.w -= 1;
        clip.h -= 1;
    }
    if (background.a != 0)
        target->fill(tpos, background);
    Text::render(clip, dest, target);
}

Button::Button() : Label()
{
    background = Colour();
    hoverBackground = Colour();
    pos = Rect();
    mHasMouse = false;
    mHover = false;
    drawFrame = true;
    threeDimensionalEffect = true;
}

Button::Button(Rect cpos, std::string ctext, Container* parent) : Label()
{
    background = Colour();
    hoverBackground = Colour();
    pos = cpos;
    mHasMouse = false;
    mHover = false;
    drawFrame = true;
    threeDimensionalEffect = true;
    text = ctext;
    if (parent != NULL)
        parent->add(this);
}

void Button::render()
{
    redraw();
    Rect tpos = pos;
    tpos.trim(1);

    // If the background is fully transparent, what's the point?
    if ((mHover || mHasMouse) && hoverBackground.a != 0)
    {
        target->fill(pos, hoverBackground);
    }
    else if (background.a != 0)
    {
        target->fill(pos, background);
    }
    if (drawFrame)
    {
        Element::drawFrame(mHasMouse, pos);
    }
    Label::render(tpos, (threeDimensionalEffect && mHasMouse));
}

void Button::event(const SDL_Event &event)
{
    if (event.type == SDL_MOUSEMOTION)
    {
        if (pos.isEnclosing(Rect(event.button.x, event.button.y)))
            mHover = true;
        else
            mHover = false;
        return;
    }
    if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == 1)
    {
        if (pos.isEnclosing(Rect(event.button.x, event.button.y)))
            mHasMouse = true;
        return;
    }
    if (event.type == SDL_MOUSEBUTTONUP)
    {
        if (mHasMouse && event.button.button == 1)
        {
            mHasMouse = false;
            Rect mouse(event.button.x, event.button.y);
            if (pos.isEnclosing(Rect(event.button.x, event.button.y)))
            {
                Event e(this);
                e.push();
                return;
            }
        }
        return;
    }
}

SBar::SBar()
{
    colour = Colour();
    background = Colour();
    pos = Rect();
    mDragStartY = 0;
    mDragValue = 0;
    mHasMouse = false;
    max = 100;
    value = 0;
    offset = 0;
    minSize = 10;
    drawFrame = true;
}

void SBar::render()
{
    if (value > max)
        value = max;
    unsigned long int barsize = pos.h - minSize;
    // The r here means relative
    unsigned long int rbuttonsize = offset + 1;
    unsigned long int rbarsize = max + offset + 1;

    Rect box = pos;
    box.h = (rbuttonsize *  barsize / rbarsize) + minSize;
    box.y += value * barsize / rbarsize;

    // This is purely cosmetic. Due to round off errors, there may be
    // a one pixel gap at the bottom of the bar. This fixes it.
    //if (value == max)
    //box.h = pos.h - box.y;

    // If the background is fully transparent, what's the point?
    if (background.a != 0)
        target->fill(pos, background);
    target->fill(box, colour);
    if (drawFrame)
        Element::drawFrame(mHasMouse, box);
}

void SBar::event(const SDL_Event &event)
{
    if (event.type == SDL_MOUSEMOTION && mHasMouse)
    {
        Rect mouse(event.motion.x, event.motion.y);
        long int diff = mouse.y - mDragStartY;
        long int change = diff * (long int)(max + offset + 1) / (long int)(pos.h - minSize);

        if ((long int)mDragValue + change > 0)
        {
            if ((long int)mDragValue + change < (long int)max)
                value = mDragValue + change;
            else
                value = max;
        }
        else
        {
            value = 0;
        }
        return;
    }
    if (event.type == SDL_MOUSEBUTTONDOWN)
    {
        Rect mouse(event.button.x, event.button.y);
        if (event.button.button == 1 && pos.isEnclosing(mouse))
        {
            mHasMouse = true;
            mDragValue = value;
            mDragStartY = mouse.y;
        }
        return;
    }
    if (event.type == SDL_MOUSEBUTTONUP)
    {
        if (mHasMouse && event.button.button == 1)
        {
            mHasMouse = false;
        }
        if (pos.isEnclosing(Rect(event.button.x, event.button.y)))
        {
            if (event.button.button == 4 && value > 0)
                value--;
            else if (event.button.button == 5 && value < max)
                value++;
        }
        return;
    }
}

ListContainer::ListContainer() : Element(), scrollbar()
{
    renderer = NULL;
    selection = 0;
    colour = Colour();
    background = Colour();
    activeBackground = Colour();
    scrollWidth = 16;
    itemHeight = 24;
    scrollbar.offset = 0;
    scrollbar.value = 0;
    scrollbar.max = 0;
}

ListContainer::ListContainer(Rect cpos, std::vector <std::string>* clist, Container* parent) : Element(), scrollbar()
{
    renderer = NULL;
    pos = cpos;
    selection = 0;
    colour = Colour();
    background = Colour();
    activeBackground = Colour();
    scrollWidth = 16;
    itemHeight = 24;
    list = clist;
    if (parent != NULL)
        parent->add(this);
    recalcSize();
}

std::string ListContainer::getItem(int number)
{
    return (*list)[number];
}

unsigned int ListContainer::getSize()
{
    return list->size();
}


void ListContainer::recalcSize()
{
    if (oldpos == pos)
        return;
    if (listPos.w > -scrollWidth)
        return;
    listPos = pos;
    listPos.w -= scrollWidth;

    if (scrollbar.offset > 0 && renderer != NULL)
        delete []renderer;

    scrollbar.pos = pos;
    scrollbar.pos.x += pos.w - scrollWidth;
    scrollbar.pos.w = scrollWidth;
    scrollbar.offset = listPos.h / itemHeight;
    if (scrollbar.offset > 0)
    {
        renderer = new Label[scrollbar.offset];
        for (unsigned int i = 0; i < scrollbar.offset; i++)
        {
            renderer[i].font = font;
            renderer[i].align = align;
        }
    }
}

void ListContainer::render()
{
    recalcSize();
    if (getSize() <= scrollbar.offset)
    {
        scrollbar.max = 0;
    }
    else
    {
        scrollbar.max = getSize() - scrollbar.offset;
        scrollbar.render();
    }
    target->fill(listPos, background);
    for (unsigned long int i = 0; i  + scrollbar.value < getSize() && i < scrollbar.offset; i++)
    {
        Rect itemPos = pos;
        itemPos.w -= scrollWidth;
        itemPos.h = itemHeight;
        itemPos.y += itemHeight * i;
        if (i + scrollbar.value == selection)
            target->fill(itemPos, activeBackground);
        itemPos.w -= 4;
        itemPos.x += 2;
        renderer[i].colour = colour;
        renderer[i].text = getItem(i + scrollbar.value);
        renderer[i].render(itemPos);
    }
}

void ListContainer::event(const SDL_Event &event)
{
    scrollbar.event(event);
    if (event.type == SDL_MOUSEBUTTONDOWN && listPos.isEnclosing(Rect(event.button.x, event.button.y)))
    {
        if (event.button.button == 1)
        {
            unsigned int result = (event.button.y - listPos.y) / itemHeight + scrollbar.value;
            if (result < getSize())
                selection = result;
        }
        if (event.button.button == 4 && scrollbar.value > 0)
            scrollbar.value--;
        else if (event.button.button == 5 && scrollbar.value < scrollbar.max)
            scrollbar.value++;
        return;
    }
}

void Container::add(Element* child)
{
    children.push_back(child);
}

void Container::render()
{
    std::vector <Element*>::iterator p;
    for (p = children.begin(); p != children.end(); p++)
    {
        (*p)->render();
    }
}

void Container::event(const SDL_Event &event)
{
    std::vector <Element*>::iterator p;
    for (p = children.begin(); p != children.end(); p++)
    {
        (*p)->event(event);
    }
}

//                  GU-I
const int EVENTID = 7219;

Event::Event() {}


Event::Event(SDL_Event &cEvent)
{
    mEvent = cEvent;
}

bool Event::valid()
{
    if (mEvent.type == SDL_USEREVENT && mEvent.user.code == EVENTID)
        return true;
    return false;
}

Element* Event::source()
{
    return (Element*) mEvent.user.data1;
}

Event::Event(Element* src)
{
    mEvent.user.code = EVENTID;
    mEvent.user.data1 = (void*)src;
}

void Event::push()
{
    mEvent.type = SDL_USEREVENT;
    SDL_PushEvent(&mEvent);
}

void Event::saveMouse()
{
    SDL_GetMouseState(&mouseX, &mouseY);
}

void Event::pushMouse()
{
    int x, y;
    Uint8 buttons = SDL_GetMouseState(&x, &y);
    mEvent.type = SDL_MOUSEMOTION;
    mEvent.motion.x = x;
    mEvent.motion.y = y;
    mEvent.motion.xrel = x - mouseX;
    mEvent.motion.yrel = y - mouseY;
    mEvent.motion.state = buttons;
    SDL_PushEvent(&mEvent);
}

}
} // namespace gui & sdl
